/***********************************************************************
*
* Copyright (c) 2012-2022 Barbara Geller
* Copyright (c) 2012-2022 Ansel Sermersheim
*
* Copyright (c) 2015 The Qt Company Ltd.
* Copyright (c) 2012-2016 Digia Plc and/or its subsidiary(-ies).
* Copyright (c) 2008-2012 Nokia Corporation and/or its subsidiary(-ies).
*
* This file is part of CopperSpice.
*
* CopperSpice is free software. You can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* version 2.1 as published by the Free Software Foundation.
*
* CopperSpice is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*
* https://www.gnu.org/licenses/
*
***********************************************************************/

#include <qlistview.h>

#ifndef QT_NO_LISTVIEW

#include <qabstractitemdelegate.h>
#include <qapplication.h>
#include <qdrag.h>
#include <qpainter.h>
#include <qbitmap.h>
#include <qvector.h>
#include <qstyle.h>
#include <qevent.h>
#include <qscrollbar.h>
#include <qrubberband.h>
#include <qlistview_p.h>
#include <qdebug.h>

#ifndef QT_NO_ACCESSIBILITY
#include <qaccessible.h>
#endif

extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event);

QListView::QListView(QWidget *parent)
   : QAbstractItemView(*new QListViewPrivate, parent)
{
   setViewMode(ListMode);
   setSelectionMode(SingleSelection);
   setAttribute(Qt::WA_MacShowFocusRect);
   Q_D(QListView);               // We rely on a qobject_cast for PM_DefaultFrameWidth to change
   d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed
}

/*!
  \internal
*/
QListView::QListView(QListViewPrivate &dd, QWidget *parent)
   : QAbstractItemView(dd, parent)
{
   setViewMode(ListMode);
   setSelectionMode(SingleSelection);
   setAttribute(Qt::WA_MacShowFocusRect);
   Q_D(QListView);               // We rely on a qobject_cast for PM_DefaultFrameWidth to change
   d->updateStyledFrameWidths(); // hence we have to force an update now that the object has been constructed
}

QListView::~QListView()
{
}

void QListView::setMovement(Movement movement)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::Movement);
   d->movement = movement;

#ifndef QT_NO_DRAGANDDROP
   bool movable = (movement != Static);
   setDragEnabled(movable);
   d->viewport->setAcceptDrops(movable);
#endif
   d->doDelayedItemsLayout();
}

QListView::Movement QListView::movement() const
{
   Q_D(const QListView);
   return d->movement;
}

void QListView::setFlow(Flow flow)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::Flow);
   d->flow = flow;
   d->doDelayedItemsLayout();
}

QListView::Flow QListView::flow() const
{
   Q_D(const QListView);
   return d->flow;
}

void QListView::setWrapping(bool enable)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::Wrap);
   d->setWrapping(enable);
   d->doDelayedItemsLayout();
}

bool QListView::isWrapping() const
{
   Q_D(const QListView);
   return d->isWrapping();
}

void QListView::setResizeMode(ResizeMode mode)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::ResizeMode);
   d->resizeMode = mode;
}

QListView::ResizeMode QListView::resizeMode() const
{
   Q_D(const QListView);
   return d->resizeMode;
}

void QListView::setLayoutMode(LayoutMode mode)
{
   Q_D(QListView);
   d->layoutMode = mode;
}

QListView::LayoutMode QListView::layoutMode() const
{
   Q_D(const QListView);
   return d->layoutMode;
}

// ### Qt5 - Use same semantic as layouts (spacing is the size of space *between* items)
void QListView::setSpacing(int space)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::Spacing);
   d->setSpacing(space);
   d->doDelayedItemsLayout();
}

int QListView::spacing() const
{
   Q_D(const QListView);
   return d->spacing();
}

void QListView::setBatchSize(int batchSize)
{
   Q_D(QListView);
   if (batchSize <= 0) {
      qWarning("Invalid batchSize (%d)", batchSize);
      return;
   }
   d->batchSize = batchSize;
}

int QListView::batchSize() const
{
   Q_D(const QListView);
   return d->batchSize;
}

void QListView::setGridSize(const QSize &size)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::GridSize);
   d->setGridSize(size);
   d->doDelayedItemsLayout();
}

QSize QListView::gridSize() const
{
   Q_D(const QListView);
   return d->gridSize();
}

void QListView::setViewMode(ViewMode mode)
{
   Q_D(QListView);
   if (d->commonListView && d->viewMode == mode) {
      return;
   }
   d->viewMode = mode;

   delete d->commonListView;
   if (mode == ListMode) {
      d->commonListView = new QListModeViewBase(this, d);
      if (!(d->modeProperties & QListViewPrivate::Wrap)) {
         d->setWrapping(false);
      }
      if (!(d->modeProperties & QListViewPrivate::Spacing)) {
         d->setSpacing(0);
      }
      if (!(d->modeProperties & QListViewPrivate::GridSize)) {
         d->setGridSize(QSize());
      }
      if (!(d->modeProperties & QListViewPrivate::Flow)) {
         d->flow = TopToBottom;
      }
      if (!(d->modeProperties & QListViewPrivate::Movement)) {
         d->movement = Static;
      }
      if (!(d->modeProperties & QListViewPrivate::ResizeMode)) {
         d->resizeMode = Fixed;
      }
      if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) {
         d->showElasticBand = false;
      }
   } else {
      d->commonListView = new QIconModeViewBase(this, d);
      if (!(d->modeProperties & QListViewPrivate::Wrap)) {
         d->setWrapping(true);
      }
      if (!(d->modeProperties & QListViewPrivate::Spacing)) {
         d->setSpacing(0);
      }
      if (!(d->modeProperties & QListViewPrivate::GridSize)) {
         d->setGridSize(QSize());
      }
      if (!(d->modeProperties & QListViewPrivate::Flow)) {
         d->flow = LeftToRight;
      }
      if (!(d->modeProperties & QListViewPrivate::Movement)) {
         d->movement = Free;
      }
      if (!(d->modeProperties & QListViewPrivate::ResizeMode)) {
         d->resizeMode = Fixed;
      }
      if (!(d->modeProperties & QListViewPrivate::SelectionRectVisible)) {
         d->showElasticBand = true;
      }
   }

#ifndef QT_NO_DRAGANDDROP
   bool movable = (d->movement != Static);
   setDragEnabled(movable);
   setAcceptDrops(movable);
#endif
   d->clear();
   d->doDelayedItemsLayout();
}

QListView::ViewMode QListView::viewMode() const
{
   Q_D(const QListView);
   return d->viewMode;
}

void QListView::clearPropertyFlags()
{
   Q_D(QListView);
   d->modeProperties = 0;
}

bool QListView::isRowHidden(int row) const
{
   Q_D(const QListView);
   return d->isHidden(row);
}

void QListView::setRowHidden(int row, bool hide)
{
   Q_D(QListView);
   const bool hidden = d->isHidden(row);
   if (hide && !hidden) {
      d->commonListView->appendHiddenRow(row);
   } else if (!hide && hidden) {
      d->commonListView->removeHiddenRow(row);
   }
   d->doDelayedItemsLayout();
   d->viewport->update();
}

/*!
  \reimp
*/
QRect QListView::visualRect(const QModelIndex &index) const
{
   Q_D(const QListView);
   return d->mapToViewport(rectForIndex(index));
}

/*!
  \reimp
*/
void QListView::scrollTo(const QModelIndex &index, ScrollHint hint)
{
   Q_D(QListView);

   if (index.parent() != d->root || index.column() != d->column) {
      return;
   }

   const QRect rect = visualRect(index);
   if (hint == EnsureVisible && d->viewport->rect().contains(rect)) {
      d->viewport->update(rect);
      return;
   }

   if (d->flow == QListView::TopToBottom || d->isWrapping()) { // vertical
      verticalScrollBar()->setValue(d->verticalScrollToValue(index, rect, hint));
   }

   if (d->flow == QListView::LeftToRight || d->isWrapping()) { // horizontal
      horizontalScrollBar()->setValue(d->horizontalScrollToValue(index, rect, hint));
   }
}

int QListViewPrivate::horizontalScrollToValue(const QModelIndex &index, const QRect &rect,
   QListView::ScrollHint hint) const
{
   Q_Q(const QListView);
   const QRect area = viewport->rect();
   const bool leftOf = q->isRightToLeft()
      ? (rect.left() < area.left()) && (rect.right() < area.right())
      : rect.left() < area.left();
   const bool rightOf = q->isRightToLeft()
      ? rect.right() > area.right()
      : (rect.right() > area.right()) && (rect.left() > area.left());
   return commonListView->horizontalScrollToValue(q->visualIndex(index), hint, leftOf, rightOf, area, rect);
}

int QListViewPrivate::verticalScrollToValue(const QModelIndex &index, const QRect &rect,
   QListView::ScrollHint hint) const
{
   Q_Q(const QListView);
   const QRect area = viewport->rect();
   const bool above = (hint == QListView::EnsureVisible && rect.top() < area.top());
   const bool below = (hint == QListView::EnsureVisible && rect.bottom() > area.bottom());
   return commonListView->verticalScrollToValue(q->visualIndex(index), hint, above, below, area, rect);
}

void QListViewPrivate::selectAll(QItemSelectionModel::SelectionFlags command)
{
   if (!selectionModel) {
      return;
   }

   QItemSelection selection;
   QModelIndex topLeft;
   int row = 0;
   const int colCount = model->columnCount(root);
   for (; row < model->rowCount(root); ++row) {
      if (isHidden(row)) {
         //it might be the end of a selection range
         if (topLeft.isValid()) {
            QModelIndex bottomRight = model->index(row - 1, colCount - 1, root);
            selection.append(QItemSelectionRange(topLeft, bottomRight));
            topLeft = QModelIndex();
         }
         continue;
      }

      if (!topLeft.isValid()) { //start of a new selection range
         topLeft = model->index(row, 0, root);
      }
   }

   if (topLeft.isValid()) {
      //last selected range
      QModelIndex bottomRight = model->index(row - 1, colCount - 1, root);
      selection.append(QItemSelectionRange(topLeft, bottomRight));
   }

   if (!selection.isEmpty()) {
      selectionModel->select(selection, command);
   }
}

/*!
  \reimp

  We have a QListView way of knowing what elements are on the viewport
  through the intersectingSet function
*/
QItemViewPaintPairs QListViewPrivate::draggablePaintPairs(const QModelIndexList &indexes, QRect *r) const
{
   Q_ASSERT(r);
   Q_Q(const QListView);

   QRect &rect = *r;
   const QRect viewportRect = viewport->rect();

   QItemViewPaintPairs ret;
   const QSet<QModelIndex> visibleIndexes = intersectingSet(viewportRect.translated(q->horizontalOffset(),
            q->verticalOffset())).toList().toSet();

   for (int i = 0; i < indexes.count(); ++i) {
      const QModelIndex &index = indexes.at(i);
      if (visibleIndexes.contains(index)) {
         const QRect current = q->visualRect(index);
         ret += qMakePair(current, index);
         rect |= current;
      }
   }
   rect &= viewportRect;
   return ret;
}

/*!
  \internal
*/
void QListView::reset()
{
   Q_D(QListView);
   d->clear();
   d->hiddenRows.clear();
   QAbstractItemView::reset();
}

/*!
  \internal
*/
void QListView::setRootIndex(const QModelIndex &index)
{
   Q_D(QListView);
   d->column = qBound(0, d->column, d->model->columnCount(index) - 1);
   QAbstractItemView::setRootIndex(index);
   // sometimes we get an update before reset() is called
   d->clear();
   d->hiddenRows.clear();
}

/*!
    \internal

    Scroll the view contents by \a dx and \a dy.
*/

void QListView::scrollContentsBy(int dx, int dy)
{
   Q_D(QListView);
   d->delayedAutoScroll.stop(); // auto scroll was canceled by the user scrolling
   d->commonListView->scrollContentsBy(dx, dy, d->state == QListView::DragSelectingState);
}

/*!
    \internal

    Resize the internal contents to \a width and \a height and set the
    scroll bar ranges accordingly.
*/
void QListView::resizeContents(int width, int height)
{
   Q_D(QListView);
   d->setContentsSize(width, height);
}

// internal
QSize QListView::contentsSize() const
{
   Q_D(const QListView);
   return d->contentsSize();
}

void QListView::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
{
   d_func()->commonListView->dataChanged(topLeft, bottomRight);
   QAbstractItemView::dataChanged(topLeft, bottomRight, roles);
}

void QListView::rowsInserted(const QModelIndex &parent, int start, int end)
{
   Q_D(QListView);
   // ### be smarter about inserted items
   d->clear();
   d->doDelayedItemsLayout();
   QAbstractItemView::rowsInserted(parent, start, end);
}

void QListView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
   Q_D(QListView);
   // if the parent is above d->root in the tree, nothing will happen
   QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
   if (parent == d->root) {
      QSet<QPersistentModelIndex>::iterator it = d->hiddenRows.begin();
      while (it != d->hiddenRows.end()) {
         int hiddenRow = it->row();
         if (hiddenRow >= start && hiddenRow <= end) {
            it = d->hiddenRows.erase(it);
         } else {
            ++it;
         }
      }
   }
   d->clear();
   d->doDelayedItemsLayout();
}

/*!
  \reimp
*/
void QListView::mouseMoveEvent(QMouseEvent *e)
{
   if (!isVisible()) {
      return;
   }
   Q_D(QListView);
   QAbstractItemView::mouseMoveEvent(e);
   if (state() == DragSelectingState
      && d->showElasticBand
      && d->selectionMode != SingleSelection
      && d->selectionMode != NoSelection) {
      QRect rect(d->pressedPosition, e->pos() + QPoint(horizontalOffset(), verticalOffset()));
      rect = rect.normalized();
      d->viewport->update(d->mapToViewport(rect.united(d->elasticBand)));
      d->elasticBand = rect;
   }
}

/*!
  \reimp
*/
void QListView::mouseReleaseEvent(QMouseEvent *e)
{
   Q_D(QListView);
   QAbstractItemView::mouseReleaseEvent(e);
   // #### move this implementation into a dynamic class
   if (d->showElasticBand && d->elasticBand.isValid()) {
      d->viewport->update(d->mapToViewport(d->elasticBand));
      d->elasticBand = QRect();
   }
}
#ifndef QT_NO_WHEELEVENT
void QListView::wheelEvent(QWheelEvent *e)
{
   Q_D(QListView);

   if (e->orientation() == Qt::Vertical) {
      if (e->angleDelta().x() == 0 && ((d->flow == TopToBottom && d->wrap) || (d->flow == LeftToRight && ! d->wrap))
            && d->vbar->minimum() == 0 && d->vbar->maximum() == 0)  {

         QPoint pixelDelta(e->pixelDelta().y(), e->pixelDelta().x());
         QPoint angleDelta(e->angleDelta().y(), e->angleDelta().x());

         QWheelEvent hwe(e->pos(), e->globalPos(), pixelDelta, angleDelta, e->buttons(), e->modifiers(), e->phase(), e->source());

         if (e->spontaneous()) {
            qt_sendSpontaneousEvent(d->hbar, &hwe);
         } else {
            QApplication::sendEvent(d->hbar, &hwe);
         }

         e->setAccepted(hwe.isAccepted());

      } else {
         QApplication::sendEvent(d->vbar, e);
      }

   } else {
      QApplication::sendEvent(d->hbar, e);
   }
}
#endif

void QListView::timerEvent(QTimerEvent *e)
{
   Q_D(QListView);

   if (e->timerId() == d->batchLayoutTimer.timerId()) {

      if (d->doItemsLayout(d->batchSize)) {
         // layout is done
         d->batchLayoutTimer.stop();
         updateGeometries();
         d->viewport->update();
      }
   }

   QAbstractItemView::timerEvent(e);
}

/*!
  \reimp
*/
void QListView::resizeEvent(QResizeEvent *e)
{
   Q_D(QListView);
   if (d->delayedPendingLayout) {
      return;
   }

   QSize delta = e->size() - e->oldSize();

   if (delta.isNull()) {
      return;
   }

   bool listWrap = (d->viewMode == ListMode) && d->wrapItemText;
   bool flowDimensionChanged = (d->flow == LeftToRight && delta.width() != 0)
      || (d->flow == TopToBottom && delta.height() != 0);

   // We post a delayed relayout in the following cases :
   // - we're wrapping
   // - the state is NoState, we're adjusting and the size has changed in the flowing direction
   if (listWrap
      || (state() == NoState && d->resizeMode == Adjust && flowDimensionChanged)) {
      d->doDelayedItemsLayout(100); // wait 1/10 sec before starting the layout
   } else {
      QAbstractItemView::resizeEvent(e);
   }
}

#ifndef QT_NO_DRAGANDDROP

/*!
  \reimp
*/
void QListView::dragMoveEvent(QDragMoveEvent *e)
{
   Q_D(QListView);
   if (!d->commonListView->filterDragMoveEvent(e)) {
      if (viewMode() == QListView::ListMode && flow() == QListView::LeftToRight) {
         static_cast<QListModeViewBase *>(d->commonListView)->dragMoveEvent(e);
      } else {
         QAbstractItemView::dragMoveEvent(e);
      }
   }
}


/*!
  \reimp
*/
void QListView::dragLeaveEvent(QDragLeaveEvent *e)
{
   if (!d_func()->commonListView->filterDragLeaveEvent(e)) {
      QAbstractItemView::dragLeaveEvent(e);
   }
}

/*!
  \reimp
*/
void QListView::dropEvent(QDropEvent *e)
{
   if (!d_func()->commonListView->filterDropEvent(e)) {
      QAbstractItemView::dropEvent(e);
   }
}

/*!
  \reimp
*/
void QListView::startDrag(Qt::DropActions supportedActions)
{
   if (!d_func()->commonListView->filterStartDrag(supportedActions)) {
      QAbstractItemView::startDrag(supportedActions);
   }
}
#endif // QT_NO_DRAGANDDROP

/*!
  \reimp
*/
QStyleOptionViewItem QListView::viewOptions() const
{
   Q_D(const QListView);
   QStyleOptionViewItem option = QAbstractItemView::viewOptions();

   if (!d->iconSize.isValid()) { // otherwise it was already set in abstractitemview
      int pm = (d->viewMode == QListView::ListMode
            ? style()->pixelMetric(QStyle::PM_ListViewIconSize, nullptr, this)
            : style()->pixelMetric(QStyle::PM_IconViewIconSize, nullptr, this));
      option.decorationSize = QSize(pm, pm);
   }

   if (d->viewMode == QListView::IconMode) {
      option.showDecorationSelected = false;
      option.decorationPosition = QStyleOptionViewItem::Top;
      option.displayAlignment = Qt::AlignCenter;
   } else {
      option.decorationPosition = QStyleOptionViewItem::Left;
   }

   if (d->gridSize().isValid()) {
      option.rect.setSize(d->gridSize());
   }

   return option;
}


/*!
  \reimp
*/
void QListView::paintEvent(QPaintEvent *e)
{
   Q_D(QListView);
   if (!d->itemDelegate) {
      return;
   }

   QStyleOptionViewItem option = d->viewOptions();
   QPainter painter(d->viewport);

   const QVector<QModelIndex> toBeRendered = d->intersectingSet(e->rect().translated(horizontalOffset(), verticalOffset()), false);

   const QModelIndex current = currentIndex();
   const QModelIndex hover = d->hover;
   const QAbstractItemModel *itemModel = d->model;
   const QItemSelectionModel *selections = d->selectionModel;
   const bool focus = (hasFocus() || d->viewport->hasFocus()) && current.isValid();
   const bool alternate = d->alternatingColors;
   const QStyle::State state = option.state;
   const QAbstractItemView::State viewState = this->state();
   const bool enabled = (state & QStyle::State_Enabled) != 0;

   bool alternateBase = false;
   int previousRow = -2; // trigger the alternateBase adjustment on first pass

   int maxSize = (flow() == TopToBottom)
      ? qMax(viewport()->size().width(), d->contentsSize().width()) - 2 * d->spacing()
      : qMax(viewport()->size().height(), d->contentsSize().height()) - 2 * d->spacing();

   QVector<QModelIndex>::const_iterator end = toBeRendered.constEnd();
   for (QVector<QModelIndex>::const_iterator it = toBeRendered.constBegin(); it != end; ++it) {
      Q_ASSERT((*it).isValid());
      option.rect = visualRect(*it);

      if (flow() == TopToBottom) {
         option.rect.setWidth(qMin(maxSize, option.rect.width()));
      } else {
         option.rect.setHeight(qMin(maxSize, option.rect.height()));
      }

      option.state = state;
      if (selections && selections->isSelected(*it)) {
         option.state |= QStyle::State_Selected;
      }
      if (enabled) {
         QPalette::ColorGroup cg;
         if ((itemModel->flags(*it) & Qt::ItemIsEnabled) == 0) {
            option.state &= ~QStyle::State_Enabled;
            cg = QPalette::Disabled;
         } else {
            cg = QPalette::Normal;
         }
         option.palette.setCurrentColorGroup(cg);
      }
      if (focus && current == *it) {
         option.state |= QStyle::State_HasFocus;
         if (viewState == EditingState) {
            option.state |= QStyle::State_Editing;
         }
      }

      if (*it == hover) {
         option.state |= QStyle::State_MouseOver;
      } else {
         option.state &= ~QStyle::State_MouseOver;
      }

      if (alternate) {
         int row = (*it).row();
         if (row != previousRow + 1) {
            // adjust alternateBase according to rows in the "gap"
            if (!d->hiddenRows.isEmpty()) {
               for (int r = qMax(previousRow + 1, 0); r < row; ++r) {
                  if (!d->isHidden(r)) {
                     alternateBase = !alternateBase;
                  }
               }
            } else {
               alternateBase = (row & 1) != 0;
            }
         }

         if (alternateBase) {
            option.features |= QStyleOptionViewItem::Alternate;
         } else {
            option.features &= ~QStyleOptionViewItem::Alternate;
         }

         // draw background of the item (only alternate row). rest of the background
         // is provided by the delegate
         QStyle::State oldState = option.state;
         option.state &= ~QStyle::State_Selected;
         style()->drawPrimitive(QStyle::PE_PanelItemViewRow, &option, &painter, this);
         option.state = oldState;

         alternateBase = !alternateBase;
         previousRow = row;
      }

      d->delegateForIndex(*it)->paint(&painter, option, *it);
   }

#ifndef QT_NO_DRAGANDDROP
   d->commonListView->paintDragDrop(&painter);
#endif

#ifndef QT_NO_RUBBERBAND
   // #### move this implementation into a dynamic class
   if (d->showElasticBand && d->elasticBand.isValid()) {
      QStyleOptionRubberBand opt;
      opt.initFrom(this);
      opt.shape = QRubberBand::Rectangle;
      opt.opaque = false;
      opt.rect = d->mapToViewport(d->elasticBand, false).intersected(
            d->viewport->rect().adjusted(-16, -16, 16, 16));
      painter.save();
      style()->drawControl(QStyle::CE_RubberBand, &opt, &painter);
      painter.restore();
   }
#endif
}

/*!
  \reimp
*/
QModelIndex QListView::indexAt(const QPoint &p) const
{
   Q_D(const QListView);
   QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
   const QVector<QModelIndex> intersectVector = d->intersectingSet(rect);
   QModelIndex index = intersectVector.count() > 0
      ? intersectVector.last() : QModelIndex();
   if (index.isValid() && visualRect(index).contains(p)) {
      return index;
   }
   return QModelIndex();
}

/*!
  \reimp
*/
int QListView::horizontalOffset() const
{
   return d_func()->commonListView->horizontalOffset();
}

/*!
  \reimp
*/
int QListView::verticalOffset() const
{
   return d_func()->commonListView->verticalOffset();
}

/*!
  \reimp
*/
QModelIndex QListView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
{
   Q_D(QListView);
   Q_UNUSED(modifiers);

   QModelIndex current = currentIndex();
   if (!current.isValid()) {
      int rowCount = d->model->rowCount(d->root);
      if (!rowCount) {
         return QModelIndex();
      }
      int row = 0;
      while (row < rowCount && d->isHiddenOrDisabled(row)) {
         ++row;
      }
      if (row >= rowCount) {
         return QModelIndex();
      }
      return d->model->index(row, d->column, d->root);
   }

   const QRect initialRect = rectForIndex(current);
   QRect rect = initialRect;
   if (rect.isEmpty()) {
      return d->model->index(0, d->column, d->root);
   }

   if (d->gridSize().isValid()) {
      rect.setSize(d->gridSize());
   }

   QSize contents = d->contentsSize();
   QVector<QModelIndex> intersectVector;

   switch (cursorAction) {
      case MoveLeft:
         while (intersectVector.isEmpty()) {
            rect.translate(-rect.width(), 0);
            if (rect.right() <= 0) {
               return current;
            }
            if (rect.left() < 0) {
               rect.setLeft(0);
            }
            intersectVector = d->intersectingSet(rect);
            d->removeCurrentAndDisabled(&intersectVector, current);
         }

         return d->closestIndex(initialRect, intersectVector);

      case MoveRight:
         while (intersectVector.isEmpty()) {
            rect.translate(rect.width(), 0);
            if (rect.left() >= contents.width()) {
               return current;
            }
            if (rect.right() > contents.width()) {
               rect.setRight(contents.width());
            }
            intersectVector = d->intersectingSet(rect);
            d->removeCurrentAndDisabled(&intersectVector, current);
         }

         return d->closestIndex(initialRect, intersectVector);

      case MovePageUp:
         // move current by (visibileRowCount - 1) items.
         // rect.translate(0, -rect.height()); will happen in the switch fallthrough for MoveUp.
         rect.moveTop(rect.top() - d->viewport->height() + 2 * rect.height());
         if (rect.top() < rect.height()) {
            rect.moveTop(rect.height());
         }

         [[fallthrough]];

      case MovePrevious:
      case MoveUp:
         while (intersectVector.isEmpty()) {
            rect.translate(0, -rect.height());

            if (rect.bottom() <= 0) {
#ifdef QT_KEYPAD_NAVIGATION
               if (QApplication::keypadNavigationEnabled()) {
                  int row = d->batchStartRow() - 1;
                  while (row >= 0 && d->isHiddenOrDisabled(row)) {
                     --row;
                  }
                  if (row >= 0) {
                     return d->model->index(row, d->column, d->root);
                  }
               }
#endif
               return current;
            }
            if (rect.top() < 0) {
               rect.setTop(0);
            }
            intersectVector = d->intersectingSet(rect);
            d->removeCurrentAndDisabled(&intersectVector, current);
         }

         return d->closestIndex(initialRect, intersectVector);

      case MovePageDown:
         // move current by (visibileRowCount - 1) items.
         // rect.translate(0, rect.height()); will happen in the switch fallthrough for MoveDown.
         rect.moveTop(rect.top() + d->viewport->height() - 2 * rect.height());
         if (rect.bottom() > contents.height() - rect.height()) {
            rect.moveBottom(contents.height() - rect.height());
         }

         [[fallthrough]];

      case MoveNext:
      case MoveDown:
         while (intersectVector.isEmpty()) {
            rect.translate(0, rect.height());

            if (rect.top() >= contents.height()) {
#ifdef QT_KEYPAD_NAVIGATION
               if (QApplication::keypadNavigationEnabled()) {
                  int rowCount = d->model->rowCount(d->root);
                  int row = 0;
                  while (row < rowCount && d->isHiddenOrDisabled(row)) {
                     ++row;
                  }
                  if (row < rowCount) {
                     return d->model->index(row, d->column, d->root);
                  }
               }
#endif
               return current;
            }

            if (rect.bottom() > contents.height()) {
               rect.setBottom(contents.height());
            }

            intersectVector = d->intersectingSet(rect);
            d->removeCurrentAndDisabled(&intersectVector, current);
         }

         return d->closestIndex(initialRect, intersectVector);

      case MoveHome:
         return d->model->index(0, d->column, d->root);
      case MoveEnd:
         return d->model->index(d->batchStartRow() - 1, d->column, d->root);
   }

   return current;
}

QRect QListView::rectForIndex(const QModelIndex &index) const
{
   return d_func()->rectForIndex(index);
}

void QListView::setPositionForIndex(const QPoint &position, const QModelIndex &index)
{
   Q_D(QListView);
   if (d->movement == Static
      || !d->isIndexValid(index)
      || index.parent() != d->root
      || index.column() != d->column) {
      return;
   }

   d->executePostedLayout();
   d->commonListView->setPositionForIndex(position, index);
}

/*!
  \reimp
*/
void QListView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
   Q_D(QListView);
   if (!d->selectionModel) {
      return;
   }

   // if we are wrapping, we can only selecte inside the contents rectangle
   int w = qMax(d->contentsSize().width(), d->viewport->width());
   int h = qMax(d->contentsSize().height(), d->viewport->height());
   if (d->wrap && !QRect(0, 0, w, h).intersects(rect)) {
      return;
   }

   QItemSelection selection;

   if (rect.width() == 1 && rect.height() == 1) {
      const QVector<QModelIndex> intersectVector = d->intersectingSet(rect.translated(horizontalOffset(), verticalOffset()));
      QModelIndex tl;
      if (!intersectVector.isEmpty()) {
         tl = intersectVector.last();   // special case for mouse press; only select the top item
      }
      if (tl.isValid() && d->isIndexEnabled(tl)) {
         selection.select(tl, tl);
      }
   } else {
      if (state() == DragSelectingState) { // visual selection mode (rubberband selection)
         selection = d->selection(rect.translated(horizontalOffset(), verticalOffset()));
      } else { // logical selection mode (key and mouse click selection)
         QModelIndex tl, br;
         // get the first item
         const QRect topLeft(rect.left() + horizontalOffset(), rect.top() + verticalOffset(), 1, 1);
         QVector<QModelIndex> intersectVector = d->intersectingSet(topLeft);
         if (!intersectVector.isEmpty()) {
            tl = intersectVector.last();
         }
         // get the last item
         const QRect bottomRight(rect.right() + horizontalOffset(), rect.bottom() + verticalOffset(), 1, 1);
         intersectVector = d->intersectingSet(bottomRight);
         if (!intersectVector.isEmpty()) {
            br = intersectVector.last();
         }

         // get the ranges
         if (tl.isValid() && br.isValid()
            && d->isIndexEnabled(tl)
            && d->isIndexEnabled(br)) {
            QRect first = rectForIndex(tl);
            QRect last = rectForIndex(br);
            QRect middle;
            if (d->flow == LeftToRight) {
               QRect &top = first;
               QRect &bottom = last;
               // if bottom is above top, swap them
               if (top.center().y() > bottom.center().y()) {
                  QRect tmp = top;
                  top = bottom;
                  bottom = tmp;
               }
               // if the rect are on differnet lines, expand
               if (top.top() != bottom.top()) {
                  // top rectangle
                  if (isRightToLeft()) {
                     top.setLeft(0);
                  } else {
                     top.setRight(contentsSize().width());
                  }
                  // bottom rectangle
                  if (isRightToLeft()) {
                     bottom.setRight(contentsSize().width());
                  } else {
                     bottom.setLeft(0);
                  }
               } else if (top.left() > bottom.right()) {
                  if (isRightToLeft()) {
                     bottom.setLeft(top.right());
                  } else {
                     bottom.setRight(top.left());
                  }
               } else {
                  if (isRightToLeft()) {
                     top.setLeft(bottom.right());
                  } else {
                     top.setRight(bottom.left());
                  }
               }
               // middle rectangle
               if (top.bottom() < bottom.top()) {
                  if (gridSize().isValid() && !gridSize().isNull()) {
                     middle.setTop(top.top() + gridSize().height());
                  } else {
                     middle.setTop(top.bottom() + 1);
                  }
                  middle.setLeft(qMin(top.left(), bottom.left()));
                  middle.setBottom(bottom.top() - 1);
                  middle.setRight(qMax(top.right(), bottom.right()));
               }
            } else {    // TopToBottom
               QRect &left = first;
               QRect &right = last;
               if (left.center().x() > right.center().x()) {
                  qSwap(left, right);
               }

               int ch = contentsSize().height();
               if (left.left() != right.left()) {
                  // left rectangle
                  if (isRightToLeft()) {
                     left.setTop(0);
                  } else {
                     left.setBottom(ch);
                  }

                  // top rectangle
                  if (isRightToLeft()) {
                     right.setBottom(ch);
                  } else {
                     right.setTop(0);
                  }
                  // only set middle if the
                  middle.setTop(0);
                  middle.setBottom(ch);
                  if (gridSize().isValid() && !gridSize().isNull()) {
                     middle.setLeft(left.left() + gridSize().width());
                  } else {
                     middle.setLeft(left.right() + 1);
                  }
                  middle.setRight(right.left() - 1);
               } else if (left.bottom() < right.top()) {
                  left.setBottom(right.top() - 1);
               } else {
                  right.setBottom(left.top() - 1);
               }
            }

            // do the selections
            QItemSelection topSelection = d->selection(first);
            QItemSelection middleSelection = d->selection(middle);
            QItemSelection bottomSelection = d->selection(last);
            // merge
            selection.merge(topSelection, QItemSelectionModel::Select);
            selection.merge(middleSelection, QItemSelectionModel::Select);
            selection.merge(bottomSelection, QItemSelectionModel::Select);
         }
      }
   }

   d->selectionModel->select(selection, command);
}

/*!
  \reimp

  Since 4.7, the returned region only contains rectangles intersecting
  (or included in) the viewport.
*/
QRegion QListView::visualRegionForSelection(const QItemSelection &selection) const
{
   Q_D(const QListView);
   // ### NOTE: this is a potential bottleneck in non-static mode
   int c = d->column;
   QRegion selectionRegion;
   const QRect &viewportRect = d->viewport->rect();
   for (int i = 0; i < selection.count(); ++i) {
      if (!selection.at(i).isValid()) {
         continue;
      }
      QModelIndex parent = selection.at(i).topLeft().parent();
      //we only display the children of the root in a listview
      //we're not interested in the other model indexes
      if (parent != d->root) {
         continue;
      }
      int t = selection.at(i).topLeft().row();
      int b = selection.at(i).bottomRight().row();
      if (d->viewMode == IconMode || d->isWrapping()) { // in non-static mode, we have to go through all selected items
         for (int r = t; r <= b; ++r) {
            const QRect &rect = visualRect(d->model->index(r, c, parent));
            if (viewportRect.intersects(rect)) {
               selectionRegion += rect;
            }
         }
      } else { // in static mode, we can optimize a bit
         while (t <= b && d->isHidden(t)) {
            ++t;
         }
         while (b >= t && d->isHidden(b)) {
            --b;
         }
         const QModelIndex top = d->model->index(t, c, parent);
         const QModelIndex bottom = d->model->index(b, c, parent);
         QRect rect(visualRect(top).topLeft(),
            visualRect(bottom).bottomRight());
         if (viewportRect.intersects(rect)) {
            selectionRegion += rect;
         }
      }
   }

   return selectionRegion;
}

/*!
  \reimp
*/
QModelIndexList QListView::selectedIndexes() const
{
   Q_D(const QListView);

   if (! d->selectionModel) {
      return QModelIndexList();
   }

   QModelIndexList viewSelected = d->selectionModel->selectedIndexes();

   for (int i = 0; i < viewSelected.count(); ) {
      const QModelIndex &index = viewSelected.at(i);

      if (!isIndexHidden(index) && index.parent() == d->root && index.column() == d->column) {
         ++i;

      } else {
         viewSelected.removeAt(i);

      }
   }

   return viewSelected;
}

/*!
    \internal

    Layout the items according to the flow and wrapping properties.
*/
void QListView::doItemsLayout()
{
   Q_D(QListView);
   // showing the scroll bars will trigger a resize event,
   // so we set the state to expanding to avoid
   // triggering another layout

   QAbstractItemView::State oldState = state();
   setState(ExpandingState);

   if (d->model->columnCount(d->root) > 0) { // no columns means no contents
      d->resetBatchStartRow();
      if (layoutMode() == SinglePass) {
         d->doItemsLayout(d->model->rowCount(d->root));
         // layout everything
      } else if (!d->batchLayoutTimer.isActive()) {
         if (!d->doItemsLayout(d->batchSize)) {
            // layout is done
            d->batchLayoutTimer.start(0, this);   // do a new batch as fast as possible
         }
      }
   }
   QAbstractItemView::doItemsLayout();
   setState(oldState);        // restoring the oldState
}

/*!
  \reimp
*/
void QListView::updateGeometries()
{
   Q_D(QListView);
   if (geometry().isEmpty() || d->model->rowCount(d->root) <= 0 || d->model->columnCount(d->root) <= 0) {
      horizontalScrollBar()->setRange(0, 0);
      verticalScrollBar()->setRange(0, 0);
   } else {
      QModelIndex index = d->model->index(0, d->column, d->root);
      QStyleOptionViewItem option = d->viewOptions();
      QSize step = d->itemSize(option, index);
      d->commonListView->updateHorizontalScrollBar(step);
      d->commonListView->updateVerticalScrollBar(step);
   }

   QAbstractItemView::updateGeometries();

   // if the scroll bars are turned off, we resize the contents to the viewport
   if (d->movement == Static && !d->isWrapping()) {
      d->layoutChildren(); // we need the viewport size to be updated
      if (d->flow == TopToBottom) {
         if (horizontalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
            d->setContentsSize(viewport()->width(), contentsSize().height());
            horizontalScrollBar()->setRange(0, 0); // we see all the contents anyway
         }
      } else { // LeftToRight
         if (verticalScrollBarPolicy() == Qt::ScrollBarAlwaysOff) {
            d->setContentsSize(contentsSize().width(), viewport()->height());
            verticalScrollBar()->setRange(0, 0); // we see all the contents anyway
         }
      }
   }

}

/*!
  \reimp
*/
bool QListView::isIndexHidden(const QModelIndex &index) const
{
   Q_D(const QListView);
   return (d->isHidden(index.row())
         && (index.parent() == d->root)
         && index.column() == d->column);
}

/*!
    \property QListView::modelColumn
    \brief the column in the model that is visible

    By default, this property contains 0, indicating that the first
    column in the model will be shown.
*/
void QListView::setModelColumn(int column)
{
   Q_D(QListView);
   if (column < 0 || column >= d->model->columnCount(d->root)) {
      return;
   }
   d->column = column;
   d->doDelayedItemsLayout();
}

int QListView::modelColumn() const
{
   Q_D(const QListView);
   return d->column;
}


void QListView::setUniformItemSizes(bool enable)
{
   Q_D(QListView);
   d->uniformItemSizes = enable;
}

bool QListView::uniformItemSizes() const
{
   Q_D(const QListView);
   return d->uniformItemSizes;
}

void QListView::setWordWrap(bool on)
{
   Q_D(QListView);
   if (d->wrapItemText == on) {
      return;
   }
   d->wrapItemText = on;
   d->doDelayedItemsLayout();
}

bool QListView::wordWrap() const
{
   Q_D(const QListView);
   return d->wrapItemText;
}

void QListView::setSelectionRectVisible(bool show)
{
   Q_D(QListView);
   d->modeProperties |= uint(QListViewPrivate::SelectionRectVisible);
   d->setSelectionRectVisible(show);
}

bool QListView::isSelectionRectVisible() const
{
   Q_D(const QListView);
   return d->isSelectionRectVisible();
}

/*!
    \reimp
*/
bool QListView::event(QEvent *e)
{
   return QAbstractItemView::event(e);
}

QListViewPrivate::QListViewPrivate()
   : QAbstractItemViewPrivate(),
     commonListView(nullptr),
     wrap(false),
     space(0),
     flow(QListView::TopToBottom),
     movement(QListView::Static),
     resizeMode(QListView::Fixed),
     layoutMode(QListView::SinglePass),
     viewMode(QListView::ListMode),
     modeProperties(0),
     column(0),
     uniformItemSizes(false),
     batchSize(100),
     showElasticBand(false)
{
}

QListViewPrivate::~QListViewPrivate()
{
   delete commonListView;
}

void QListViewPrivate::clear()
{
   // initialization of data structs
   cachedItemSize = QSize();
   commonListView->clear();
}

void QListViewPrivate::prepareItemsLayout()
{
   Q_Q(QListView);
   clear();

   //take the size as if there were scrollbar in order to prevent scrollbar to blink
   layoutBounds = QRect(QPoint(), q->maximumViewportSize());

   int frameAroundContents = 0;
   if (q->style()->styleHint(QStyle::SH_ScrollView_FrameOnlyAroundContents)) {
      frameAroundContents = q->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) * 2;
   }

   // maximumViewportSize() already takes scrollbar into account if policy is
   // Qt::ScrollBarAlwaysOn but scrollbar extent must be deduced if policy
   // is Qt::ScrollBarAsNeeded
   int verticalMargin = vbarpolicy == Qt::ScrollBarAsNeeded
      ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, vbar) + frameAroundContents : 0;

   int horizontalMargin =  hbarpolicy == Qt::ScrollBarAsNeeded
      ? q->style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, hbar) + frameAroundContents : 0;

   layoutBounds.adjust(0, 0, -verticalMargin, -horizontalMargin);

   int rowCount = model->columnCount(root) <= 0 ? 0 : model->rowCount(root);
   commonListView->setRowCount(rowCount);
}

/*!
  \internal
*/
bool QListViewPrivate::doItemsLayout(int delta)
{
   int max = model->rowCount(root) - 1;
   int first = batchStartRow();
   int last = qMin(first + delta - 1, max);

   if (first == 0) {
      layoutChildren(); // make sure the viewport has the right size
      prepareItemsLayout();
   }

   if (max < 0 || last < first) {
      return true; // nothing to do
   }

   QListViewLayoutInfo info;
   info.bounds = layoutBounds;
   info.grid = gridSize();
   info.spacing = (info.grid.isValid() ? 0 : spacing());
   info.first = first;
   info.last = last;
   info.wrap = isWrapping();
   info.flow = flow;
   info.max = max;

   return commonListView->doBatchedItemLayout(info, max);
}

QListViewItem QListViewPrivate::indexToListViewItem(const QModelIndex &index) const
{
   if (!index.isValid() || isHidden(index.row())) {
      return QListViewItem();
   }

   return commonListView->indexToListViewItem(index);
}

QRect QListViewPrivate::mapToViewport(const QRect &rect, bool extend) const
{
   Q_Q(const QListView);
   if (!rect.isValid()) {
      return rect;
   }

   QRect result = extend ? commonListView->mapToViewport(rect) : rect;
   int dx = -q->horizontalOffset();
   int dy = -q->verticalOffset();
   return result.adjusted(dx, dy, dx, dy);
}

QModelIndex QListViewPrivate::closestIndex(const QRect &target,
   const QVector<QModelIndex> &candidates) const
{
   int distance = 0;
   int shortest = INT_MAX;
   QModelIndex closest;
   QVector<QModelIndex>::const_iterator it = candidates.begin();

   for (; it != candidates.end(); ++it) {
      if (!(*it).isValid()) {
         continue;
      }

      const QRect indexRect = indexToListViewItem(*it).rect();

      //if the center x (or y) position of an item is included in the rect of the other item,
      //we define the distance between them as the difference in x (or y) of their respective center.
      // Otherwise, we use the nahattan  length between the 2 items
      if ((target.center().x() >= indexRect.x() && target.center().x() < indexRect.right())
         || (indexRect.center().x() >= target.x() && indexRect.center().x() < target.right())) {
         //one item's center is at the vertical of the other
         distance = qAbs(indexRect.center().y() - target.center().y());
      } else if ((target.center().y() >= indexRect.y() && target.center().y() < indexRect.bottom())
         || (indexRect.center().y() >= target.y() && indexRect.center().y() < target.bottom())) {
         //one item's center is at the vertical of the other
         distance = qAbs(indexRect.center().x() - target.center().x());
      } else {
         distance = (indexRect.center() - target.center()).manhattanLength();
      }
      if (distance < shortest) {
         shortest = distance;
         closest = *it;
      }
   }
   return closest;
}

QSize QListViewPrivate::itemSize(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
   if (!uniformItemSizes) {
      const QAbstractItemDelegate *delegate = delegateForIndex(index);
      return delegate ? delegate->sizeHint(option, index) : QSize();
   }
   if (!cachedItemSize.isValid()) { // the last item is probaly the largest, so we use its size
      int row = model->rowCount(root) - 1;
      QModelIndex sample = model->index(row, column, root);
      const QAbstractItemDelegate *delegate = delegateForIndex(sample);
      cachedItemSize = delegate ? delegate->sizeHint(option, sample) : QSize();
   }
   return cachedItemSize;
}

QItemSelection QListViewPrivate::selection(const QRect &rect) const
{
   QItemSelection selection;
   QModelIndex tl, br;
   const QVector<QModelIndex> intersectVector = intersectingSet(rect);
   QVector<QModelIndex>::const_iterator it = intersectVector.begin();
   for (; it != intersectVector.end(); ++it) {
      if (!tl.isValid() && !br.isValid()) {
         tl = br = *it;
      } else if ((*it).row() == (tl.row() - 1)) {
         tl = *it; // expand current range
      } else if ((*it).row() == (br.row() + 1)) {
         br = (*it); // expand current range
      } else {
         selection.select(tl, br); // select current range
         tl = br = *it; // start new range
      }
   }

   if (tl.isValid() && br.isValid()) {
      selection.select(tl, br);
   } else if (tl.isValid()) {
      selection.select(tl, tl);
   } else if (br.isValid()) {
      selection.select(br, br);
   }

   return selection;
}

#ifndef QT_NO_DRAGANDDROP
QAbstractItemView::DropIndicatorPosition QListViewPrivate::position(const QPoint &pos, const QRect &rect,
   const QModelIndex &idx) const
{
   if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) {
      return static_cast<QListModeViewBase *>(commonListView)->position(pos, rect, idx);
   } else {
      return QAbstractItemViewPrivate::position(pos, rect, idx);
   }
}

bool QListViewPrivate::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
{
   if (viewMode == QListView::ListMode && flow == QListView::LeftToRight) {
      return static_cast<QListModeViewBase *>(commonListView)->dropOn(event, dropRow, dropCol, dropIndex);
   } else {
      return QAbstractItemViewPrivate::dropOn(event, dropRow, dropCol, dropIndex);
   }
}
#endif

/*
 * Common ListView Implementation
*/

void QCommonListViewBase::appendHiddenRow(int row)
{
   dd->hiddenRows.insert(dd->model->index(row, 0, qq->rootIndex()));
}

void QCommonListViewBase::removeHiddenRow(int row)
{
   dd->hiddenRows.remove(dd->model->index(row, 0, qq->rootIndex()));
}

#ifndef QT_NO_DRAGANDDROP
void QCommonListViewBase::paintDragDrop(QPainter *painter)
{
   // FIXME: Until the we can provide a proper drop indicator
   // in IconMode, it makes no sense to show it
   dd->paintDropIndicator(painter);
}
#endif

void QCommonListViewBase::updateHorizontalScrollBar(const QSize &step)
{
   horizontalScrollBar()->setSingleStep(step.width() + spacing());
   horizontalScrollBar()->setPageStep(viewport()->width());

   // If both scroll bars are set to auto, we might end up in a situation with enough space
   // for the actual content. But still one of the scroll bars will become enabled due to
   // the other one using the space. The other one will become invisible in the same cycle.
   // -> Infinite loop, QTBUG-39902
   const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
      qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;

   const QSize viewportSize = qq->contentsRect().size();

   bool verticalWantsToShow = contentsSize.height() > viewportSize.height();
   bool horizontalWantsToShow;
   if (verticalWantsToShow) {
      horizontalWantsToShow = contentsSize.width() > viewportSize.width() - qq->verticalScrollBar()->width();
   } else {
      horizontalWantsToShow = contentsSize.width() > viewportSize.width();
   }
   if (bothScrollBarsAuto && !horizontalWantsToShow) {
      // break the infinite loop described above by setting the range to 0, 0.
      // QAbstractScrollArea will then hide the scroll bar for us
      horizontalScrollBar()->setRange(0, 0);
   } else {
      horizontalScrollBar()->setRange(0, contentsSize.width() - viewport()->width());
   }
}

void QCommonListViewBase::updateVerticalScrollBar(const QSize &step)
{
   verticalScrollBar()->setSingleStep(step.height() + spacing());
   verticalScrollBar()->setPageStep(viewport()->height());

   // If both scroll bars are set to auto, we might end up in a situation with enough space
   // for the actual content. But still one of the scroll bars will become enabled due to
   // the other one using the space. The other one will become invisible in the same cycle.
   // -> Infinite loop, QTBUG-39902
   const bool bothScrollBarsAuto = qq->verticalScrollBarPolicy() == Qt::ScrollBarAsNeeded &&
      qq->horizontalScrollBarPolicy() == Qt::ScrollBarAsNeeded;

   const QSize viewportSize = qq->contentsRect().size();

   bool horizontalWantsToShow = contentsSize.width() > viewportSize.width();
   bool verticalWantsToShow;
   if (horizontalWantsToShow) {
      verticalWantsToShow = contentsSize.height() > viewportSize.height() - qq->horizontalScrollBar()->height();
   } else {
      verticalWantsToShow = contentsSize.height() > viewportSize.height();
   }

   if (bothScrollBarsAuto && !verticalWantsToShow) {
      // break the infinite loop described above by setting the range to 0, 0.
      // QAbstractScrollArea will then hide the scroll bar for us
      verticalScrollBar()->setRange(0, 0);
   } else {
      verticalScrollBar()->setRange(0, contentsSize.height() - viewport()->height());
   }
}

void QCommonListViewBase::scrollContentsBy(int dx, int dy, bool /*scrollElasticBand*/)
{
   dd->scrollContentsBy(isRightToLeft() ? -dx : dx, dy);
}

int QCommonListViewBase::verticalScrollToValue(int /*index*/, QListView::ScrollHint hint,
   bool above, bool below, const QRect &area, const QRect &rect) const
{
   int verticalValue = verticalScrollBar()->value();
   QRect adjusted = rect.adjusted(-spacing(), -spacing(), spacing(), spacing());
   if (hint == QListView::PositionAtTop || above) {
      verticalValue += adjusted.top();
   } else if (hint == QListView::PositionAtBottom || below) {
      verticalValue += qMin(adjusted.top(), adjusted.bottom() - area.height() + 1);
   } else if (hint == QListView::PositionAtCenter) {
      verticalValue += adjusted.top() - ((area.height() - adjusted.height()) / 2);
   }
   return verticalValue;
}

int QCommonListViewBase::horizontalOffset() const
{
   return (isRightToLeft() ? horizontalScrollBar()->maximum() - horizontalScrollBar()->value() :
         horizontalScrollBar()->value());
}

int QCommonListViewBase::horizontalScrollToValue(const int /*index*/, QListView::ScrollHint hint,
   bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
{
   int horizontalValue = horizontalScrollBar()->value();
   if (isRightToLeft()) {
      if (hint == QListView::PositionAtCenter) {
         horizontalValue += ((area.width() - rect.width()) / 2) - rect.left();
      } else {
         if (leftOf) {
            horizontalValue -= rect.left();
         } else if (rightOf) {
            horizontalValue += qMin(rect.left(), area.width() - rect.right());
         }
      }
   } else {
      if (hint == QListView::PositionAtCenter) {
         horizontalValue += rect.left() - ((area.width() - rect.width()) / 2);
      } else {
         if (leftOf) {
            horizontalValue += rect.left();
         } else if (rightOf) {
            horizontalValue += qMin(rect.left(), rect.right() - area.width());
         }
      }
   }
   return horizontalValue;
}

QListModeViewBase::QListModeViewBase(QListView *q, QListViewPrivate *d)
   : QCommonListViewBase(q, d)
{
   dd->defaultDropAction = Qt::CopyAction;
}


#ifndef QT_NO_DRAGANDDROP
QAbstractItemView::DropIndicatorPosition QListModeViewBase::position(const QPoint &pos, const QRect &rect,
   const QModelIndex &index) const
{
   QAbstractItemView::DropIndicatorPosition r = QAbstractItemView::OnViewport;
   if (!dd->overwrite) {
      const int margin = 2;
      if (pos.x() - rect.left() < margin) {
         r = QAbstractItemView::AboveItem;   // Visually, on the left
      } else if (rect.right() - pos.x() < margin) {
         r = QAbstractItemView::BelowItem;   // Visually, on the right
      } else if (rect.contains(pos, true)) {
         r = QAbstractItemView::OnItem;
      }
   } else {
      QRect touchingRect = rect;
      touchingRect.adjust(-1, -1, 1, 1);
      if (touchingRect.contains(pos, false)) {
         r = QAbstractItemView::OnItem;
      }
   }

   if (r == QAbstractItemView::OnItem && (!(dd->model->flags(index) & Qt::ItemIsDropEnabled))) {
      r = pos.x() < rect.center().x() ? QAbstractItemView::AboveItem : QAbstractItemView::BelowItem;
   }

   return r;
}

void QListModeViewBase::dragMoveEvent(QDragMoveEvent *event)
{
   if (qq->dragDropMode() == QAbstractItemView::InternalMove
      && (event->source() != qq || !(event->possibleActions() & Qt::MoveAction))) {
      return;
   }

   // ignore by default
   event->ignore();

   // can't use indexAt, doesn't account for spacing.
   QPoint p = event->pos();
   QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
   rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing());
   const QVector<QModelIndex> intersectVector = dd->intersectingSet(rect);
   QModelIndex index = intersectVector.count() > 0
      ? intersectVector.last() : QModelIndex();
   dd->hover = index;
   if (!dd->droppingOnItself(event, index)
      && dd->canDrop(event)) {

      if (index.isValid() && dd->showDropIndicator) {
         QRect rect = qq->visualRect(index);
         dd->dropIndicatorPosition = position(event->pos(), rect, index);
         // if spacing, should try to draw between items, not just next to item.
         switch (dd->dropIndicatorPosition) {
            case QAbstractItemView::AboveItem:
               if (dd->isIndexDropEnabled(index.parent())) {
                  dd->dropIndicatorRect = QRect(rect.left() - dd->spacing(), rect.top(), 0, rect.height());
                  event->accept();
               } else {
                  dd->dropIndicatorRect = QRect();
               }
               break;
            case QAbstractItemView::BelowItem:
               if (dd->isIndexDropEnabled(index.parent())) {
                  dd->dropIndicatorRect = QRect(rect.right() + dd->spacing(), rect.top(), 0, rect.height());
                  event->accept();
               } else {
                  dd->dropIndicatorRect = QRect();
               }
               break;
            case QAbstractItemView::OnItem:
               if (dd->isIndexDropEnabled(index)) {
                  dd->dropIndicatorRect = rect;
                  event->accept();
               } else {
                  dd->dropIndicatorRect = QRect();
               }
               break;
            case QAbstractItemView::OnViewport:
               dd->dropIndicatorRect = QRect();
               if (dd->isIndexDropEnabled(qq->rootIndex())) {
                  event->accept(); // allow dropping in empty areas
               }
               break;
         }
      } else {
         dd->dropIndicatorRect = QRect();
         dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
         if (dd->isIndexDropEnabled(qq->rootIndex())) {
            event->accept(); // allow dropping in empty areas
         }
      }
      dd->viewport->update();
   } // can decode

   if (dd->shouldAutoScroll(event->pos())) {
      qq->startAutoScroll();
   }
}

/*!
    If the event hasn't already been accepted, determines the index to drop on.

    if (row == -1 && col == -1)
        // append to this drop index
    else
        // place at row, col in drop index

    If it returns true a drop can be done, and dropRow, dropCol and dropIndex reflects the position of the drop.
    \internal
  */
bool QListModeViewBase::dropOn(QDropEvent *event, int *dropRow, int *dropCol, QModelIndex *dropIndex)
{
   if (event->isAccepted()) {
      return false;
   }

   QModelIndex index;
   if (dd->viewport->rect().contains(event->pos())) {
      // can't use indexAt, doesn't account for spacing.
      QPoint p = event->pos();
      QRect rect(p.x() + horizontalOffset(), p.y() + verticalOffset(), 1, 1);
      rect.adjust(-dd->spacing(), -dd->spacing(), dd->spacing(), dd->spacing());
      const QVector<QModelIndex> intersectVector = dd->intersectingSet(rect);
      index = intersectVector.count() > 0
         ? intersectVector.last() : QModelIndex();
      if (!index.isValid()) {
         index = dd->root;
      }
   }

   // If we are allowed to do the drop
   if (dd->model->supportedDropActions() & event->dropAction()) {
      int row = -1;
      int col = -1;
      if (index != dd->root) {
         dd->dropIndicatorPosition = position(event->pos(), qq->visualRect(index), index);
         switch (dd->dropIndicatorPosition) {
            case QAbstractItemView::AboveItem:
               row = index.row();
               col = index.column();
               index = index.parent();
               break;
            case QAbstractItemView::BelowItem:
               row = index.row() + 1;
               col = index.column();
               index = index.parent();
               break;
            case QAbstractItemView::OnItem:
            case QAbstractItemView::OnViewport:
               break;
         }
      } else {
         dd->dropIndicatorPosition = QAbstractItemView::OnViewport;
      }
      *dropIndex = index;
      *dropRow = row;
      *dropCol = col;
      if (!dd->droppingOnItself(event, index)) {
         return true;
      }
   }
   return false;
}

#endif //QT_NO_DRAGANDDROP

void QListModeViewBase::updateVerticalScrollBar(const QSize &step)
{
   if (verticalScrollMode() == QAbstractItemView::ScrollPerItem
      && ((flow() == QListView::TopToBottom && !isWrapping())
         || (flow() == QListView::LeftToRight && isWrapping()))) {
      const int steps = (flow() == QListView::TopToBottom ? scrollValueMap : segmentPositions).count() - 1;
      if (steps > 0) {
         const int pageSteps = perItemScrollingPageSteps(viewport()->height(), contentsSize.height(), isWrapping());
         verticalScrollBar()->setSingleStep(1);
         verticalScrollBar()->setPageStep(pageSteps);
         verticalScrollBar()->setRange(0, steps - pageSteps);
      } else {
         verticalScrollBar()->setRange(0, 0);
      }
      // } else if (vertical && d->isWrapping() && d->movement == Static) {
      // ### wrapped scrolling in flow direction
   } else {
      QCommonListViewBase::updateVerticalScrollBar(step);
   }
}

void QListModeViewBase::updateHorizontalScrollBar(const QSize &step)
{
   if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem
      && ((flow() == QListView::TopToBottom && isWrapping())
         || (flow() == QListView::LeftToRight && !isWrapping()))) {
      int steps = (flow() == QListView::TopToBottom ? segmentPositions : scrollValueMap).count() - 1;
      if (steps > 0) {
         const int pageSteps = perItemScrollingPageSteps(viewport()->width(), contentsSize.width(), isWrapping());
         horizontalScrollBar()->setSingleStep(1);
         horizontalScrollBar()->setPageStep(pageSteps);
         horizontalScrollBar()->setRange(0, steps - pageSteps);
      } else {
         horizontalScrollBar()->setRange(0, 0);
      }
   } else {
      QCommonListViewBase::updateHorizontalScrollBar(step);
   }
}

int QListModeViewBase::verticalScrollToValue(int index, QListView::ScrollHint hint,
   bool above, bool below, const QRect &area, const QRect &rect) const
{
   if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
      int value;
      if (scrollValueMap.isEmpty()) {
         value = 0;
      } else {
         int scrollBarValue = verticalScrollBar()->value();
         int numHidden = 0;
         for (int i = 0; i < flowPositions.count() - 1 && i <= scrollBarValue; ++i)
            if (isHidden(i)) {
               ++numHidden;
            }
         value = qBound(0, scrollValueMap.at(verticalScrollBar()->value()) - numHidden, flowPositions.count() - 1);
      }
      if (above) {
         hint = QListView::PositionAtTop;
      } else if (below) {
         hint = QListView::PositionAtBottom;
      }
      if (hint == QListView::EnsureVisible) {
         return value;
      }

      return perItemScrollToValue(index, value, area.height(), hint, Qt::Vertical, isWrapping(), rect.height());
   }

   return QCommonListViewBase::verticalScrollToValue(index, hint, above, below, area, rect);
}

int QListModeViewBase::horizontalOffset() const
{
   if (horizontalScrollMode() == QAbstractItemView::ScrollPerItem) {
      if (isWrapping()) {
         if (flow() == QListView::TopToBottom && !segmentPositions.isEmpty()) {
            const int max = segmentPositions.count() - 1;
            int currentValue = qBound(0, horizontalScrollBar()->value(), max);
            int position = segmentPositions.at(currentValue);
            int maximumValue = qBound(0, horizontalScrollBar()->maximum(), max);
            int maximum = segmentPositions.at(maximumValue);
            return (isRightToLeft() ? maximum - position : position);
         }
      } else if (flow() == QListView::LeftToRight && !flowPositions.isEmpty()) {
         int position = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->value()));
         int maximum = flowPositions.at(scrollValueMap.at(horizontalScrollBar()->maximum()));
         return (isRightToLeft() ? maximum - position : position);
      }
   }
   return QCommonListViewBase::horizontalOffset();
}

int QListModeViewBase::verticalOffset() const
{
   if (verticalScrollMode() == QAbstractItemView::ScrollPerItem) {
      if (isWrapping()) {
         if (flow() == QListView::LeftToRight && !segmentPositions.isEmpty()) {
            int value = verticalScrollBar()->value();
            if (value >= segmentPositions.count()) {
               return 0;
            }
            return segmentPositions.at(value) - spacing();
         }
      } else if (flow() == QListView::TopToBottom && !flowPositions.isEmpty()) {
         int value = verticalScrollBar()->value();
         if (value > scrollValueMap.count()) {
            return 0;
         }
         return flowPositions.at(scrollValueMap.at(value)) - spacing();
      }
   }
   return QCommonListViewBase::verticalOffset();
}

int QListModeViewBase::horizontalScrollToValue(int index, QListView::ScrollHint hint,
   bool leftOf, bool rightOf, const QRect &area, const QRect &rect) const
{
   if (horizontalScrollMode() != QAbstractItemView::ScrollPerItem) {
      return QCommonListViewBase::horizontalScrollToValue(index, hint, leftOf, rightOf, area, rect);
   }

   int value;
   if (scrollValueMap.isEmpty()) {
      value = 0;
   } else {
      value = qBound(0, scrollValueMap.at(horizontalScrollBar()->value()), flowPositions.count() - 1);
   }
   if (leftOf) {
      hint = QListView::PositionAtTop;
   } else if (rightOf) {
      hint = QListView::PositionAtBottom;
   }
   if (hint == QListView::EnsureVisible) {
      return value;
   }

   return perItemScrollToValue(index, value, area.width(), hint, Qt::Horizontal, isWrapping(), rect.width());
}

void QListModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
{
   // ### reorder this logic
   const int verticalValue = verticalScrollBar()->value();
   const int horizontalValue = horizontalScrollBar()->value();
   const bool vertical = (verticalScrollMode() == QAbstractItemView::ScrollPerItem);
   const bool horizontal = (horizontalScrollMode() == QAbstractItemView::ScrollPerItem);

   if (isWrapping()) {
      if (segmentPositions.isEmpty()) {
         return;
      }
      const int max = segmentPositions.count() - 1;
      if (horizontal && flow() == QListView::TopToBottom && dx != 0) {
         int currentValue = qBound(0, horizontalValue, max);
         int previousValue = qBound(0, currentValue + dx, max);
         int currentCoordinate = segmentPositions.at(currentValue) - spacing();
         int previousCoordinate = segmentPositions.at(previousValue) - spacing();
         dx = previousCoordinate - currentCoordinate;
      } else if (vertical && flow() == QListView::LeftToRight && dy != 0) {
         int currentValue = qBound(0, verticalValue, max);
         int previousValue = qBound(0, currentValue + dy, max);
         int currentCoordinate = segmentPositions.at(currentValue) - spacing();
         int previousCoordinate = segmentPositions.at(previousValue) - spacing();
         dy = previousCoordinate - currentCoordinate;
      }
   } else {
      if (flowPositions.isEmpty()) {
         return;
      }
      const int max = scrollValueMap.count() - 1;
      if (vertical && flow() == QListView::TopToBottom && dy != 0) {
         int currentValue = qBound(0, verticalValue, max);
         int previousValue = qBound(0, currentValue + dy, max);
         int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue));
         int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue));
         dy = previousCoordinate - currentCoordinate;
      } else if (horizontal && flow() == QListView::LeftToRight && dx != 0) {
         int currentValue = qBound(0, horizontalValue, max);
         int previousValue = qBound(0, currentValue + dx, max);
         int currentCoordinate = flowPositions.at(scrollValueMap.at(currentValue));
         int previousCoordinate = flowPositions.at(scrollValueMap.at(previousValue));
         dx = previousCoordinate - currentCoordinate;
      }
   }
   QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
}

bool QListModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
{
   doStaticLayout(info);
   if (batchStartRow > max) { // stop items layout
      flowPositions.resize(flowPositions.count());
      segmentPositions.resize(segmentPositions.count());
      segmentStartRows.resize(segmentStartRows.count());
      return true; // done
   }
   return false; // not done
}


/*
 * ListMode ListView Implementation
*/

// internal only
static inline int cs_vector_query(const QVector<int> &vector, int item, int start, int end)
{
   int i = (start + end + 1) >> 1;

   while (end - start > 0) {
      if (vector.at(i) > item)  {
         end = i - 1;

      }  else  {
         start = i;

      }

      i = (start + end + 1) >> 1;
   }

   return i;
}

QListViewItem QListModeViewBase::indexToListViewItem(const QModelIndex &index) const
{
   if (flowPositions.isEmpty() || segmentPositions.isEmpty() || index.row() >= flowPositions.count()) {
      return QListViewItem();
   }

   const int segment = cs_vector_query(segmentStartRows, index.row(), 0, segmentStartRows.count() - 1);

   QStyleOptionViewItem options = viewOptions();
   options.rect.setSize(contentsSize);
   QSize size = (uniformItemSizes() && cachedItemSize().isValid())
      ? cachedItemSize() : itemSize(options, index);

   QPoint pos;
   if (flow() == QListView::LeftToRight) {
      pos.setX(flowPositions.at(index.row()));
      pos.setY(segmentPositions.at(segment));

   } else { // TopToBottom
      pos.setY(flowPositions.at(index.row()));
      pos.setX(segmentPositions.at(segment));

      if (isWrapping()) {
         // make the items as wide as the segment
         int right = (segment + 1 >= segmentPositions.count()
               ? contentsSize.width()
               : segmentPositions.at(segment + 1));

         size.setWidth(right - pos.x());

      } else {
         // make the items as wide as the viewport
         size.setWidth(qMax(size.width(), viewport()->width() - 2 * spacing()));
      }
   }

   return QListViewItem(QRect(pos, size), index.row());
}

QPoint QListModeViewBase::initStaticLayout(const QListViewLayoutInfo &info)
{
   int x, y;
   if (info.first == 0) {
      flowPositions.clear();
      segmentPositions.clear();
      segmentStartRows.clear();
      segmentExtents.clear();
      scrollValueMap.clear();
      x = info.bounds.left() + info.spacing;
      y = info.bounds.top() + info.spacing;
      segmentPositions.append(info.flow == QListView::LeftToRight ? y : x);
      segmentStartRows.append(0);
   } else if (info.wrap) {
      if (info.flow == QListView::LeftToRight) {
         x = batchSavedPosition;
         y = segmentPositions.last();
      } else { // flow == QListView::TopToBottom
         x = segmentPositions.last();
         y = batchSavedPosition;
      }
   } else { // not first and not wrap
      if (info.flow == QListView::LeftToRight) {
         x = batchSavedPosition;
         y = info.bounds.top() + info.spacing;
      } else { // flow == QListView::TopToBottom
         x = info.bounds.left() + info.spacing;
         y = batchSavedPosition;
      }
   }
   return QPoint(x, y);
}

/*!
  \internal
*/
void QListModeViewBase::doStaticLayout(const QListViewLayoutInfo &info)
{
   const bool useItemSize = !info.grid.isValid();
   const QPoint topLeft = initStaticLayout(info);
   QStyleOptionViewItem option = viewOptions();
   option.rect = info.bounds;
   option.rect.adjust(info.spacing, info.spacing, -info.spacing, -info.spacing);

   // The static layout data structures are as follows:
   // One vector contains the coordinate in the direction of layout flow.
   // Another vector contains the coordinates of the segments.
   // A third vector contains the index (model row) of the first item
   // of each segment.

   int segStartPosition;
   int segEndPosition;
   int deltaFlowPosition;
   int deltaSegPosition;
   int deltaSegHint;
   int flowPosition;
   int segPosition;

   if (info.flow == QListView::LeftToRight) {
      segStartPosition = info.bounds.left();
      segEndPosition = info.bounds.width();
      flowPosition = topLeft.x();
      segPosition = topLeft.y();
      deltaFlowPosition = info.grid.width(); // dx
      deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.height(); // dy
      deltaSegHint = info.grid.height();
   } else { // flow == QListView::TopToBottom
      segStartPosition = info.bounds.top();
      segEndPosition = info.bounds.height();
      flowPosition = topLeft.y();
      segPosition = topLeft.x();
      deltaFlowPosition = info.grid.height(); // dy
      deltaSegPosition = useItemSize ? batchSavedDeltaSeg : info.grid.width(); // dx
      deltaSegHint = info.grid.width();
   }

   for (int row = info.first; row <= info.last; ++row) {
      if (isHidden(row)) { // ###
         flowPositions.append(flowPosition);
      } else {
         // if we are not using a grid, we need to find the deltas
         if (useItemSize) {
            QSize hint = itemSize(option, modelIndex(row));
            if (info.flow == QListView::LeftToRight) {
               deltaFlowPosition = hint.width() + info.spacing;
               deltaSegHint = hint.height() + info.spacing;
            } else { // TopToBottom
               deltaFlowPosition = hint.height() + info.spacing;
               deltaSegHint = hint.width() + info.spacing;
            }
         }
         // create new segment
         if (info.wrap && (flowPosition + deltaFlowPosition >= segEndPosition)) {
            segmentExtents.append(flowPosition);
            flowPosition = info.spacing + segStartPosition;
            segPosition += deltaSegPosition;
            if (info.wrap) {
               segPosition += info.spacing;
            }
            segmentPositions.append(segPosition);
            segmentStartRows.append(row);
            deltaSegPosition = 0;
         }
         // save the flow position of this item
         scrollValueMap.append(flowPositions.count());
         flowPositions.append(flowPosition);
         // prepare for the next item
         deltaSegPosition = qMax(deltaSegHint, deltaSegPosition);
         flowPosition += info.spacing + deltaFlowPosition;
      }
   }
   // used when laying out next batch
   batchSavedPosition = flowPosition;
   batchSavedDeltaSeg = deltaSegPosition;
   batchStartRow = info.last + 1;
   if (info.last == info.max) {
      flowPosition -= info.spacing;   // remove extra spacing
   }
   // set the contents size
   QRect rect = info.bounds;
   if (info.flow == QListView::LeftToRight) {
      rect.setRight(segmentPositions.count() == 1 ? flowPosition : info.bounds.right());
      rect.setBottom(segPosition + deltaSegPosition);
   } else { // TopToBottom
      rect.setRight(segPosition + deltaSegPosition);
      rect.setBottom(segmentPositions.count() == 1 ? flowPosition : info.bounds.bottom());
   }
   contentsSize = QSize(rect.right(), rect.bottom());
   // if it is the last batch, save the end of the segments
   if (info.last == info.max) {
      segmentExtents.append(flowPosition);
      scrollValueMap.append(flowPositions.count());
      flowPositions.append(flowPosition);
      segmentPositions.append(info.wrap ? segPosition + deltaSegPosition : INT_MAX);
   }
   // if the new items are visble, update the viewport
   QRect changedRect(topLeft, rect.bottomRight());
   if (clipRect().intersects(changedRect)) {
      viewport()->update();
   }
}


/*!
  \internal
  Finds the set of items intersecting with \a area.
  In this function, itemsize is counted from topleft to the start of the next item.
*/
QVector<QModelIndex> QListModeViewBase::intersectingSet(const QRect &area) const
{
   QVector<QModelIndex> ret;
   int segStartPosition;
   int segEndPosition;
   int flowStartPosition;
   int flowEndPosition;
   if (flow() == QListView::LeftToRight) {
      segStartPosition = area.top();
      segEndPosition = area.bottom();
      flowStartPosition = area.left();
      flowEndPosition = area.right();
   } else {
      segStartPosition = area.left();
      segEndPosition = area.right();
      flowStartPosition = area.top();
      flowEndPosition = area.bottom();
   }

   if (segmentPositions.count() < 2 || flowPositions.isEmpty()) {
      return ret;
   }

   // the last segment position is actually the edge of the last segment
   const int segLast = segmentPositions.count() - 2;
   int seg = cs_vector_query(segmentPositions, segStartPosition, 0, segLast + 1);

   for (; seg <= segLast && segmentPositions.at(seg) <= segEndPosition; ++seg) {
      int first = segmentStartRows.at(seg);
      int last = (seg < segLast ? segmentStartRows.at(seg + 1) : batchStartRow) - 1;

      if (segmentExtents.at(seg) < flowStartPosition) {
         continue;
      }

      int row = cs_vector_query(flowPositions, flowStartPosition, first, last );

      for (; row <= last && flowPositions.at(row) <= flowEndPosition; ++row) {
         if (isHidden(row)) {
            continue;
         }

         QModelIndex index = modelIndex(row);
         if (index.isValid()) {
            ret += index;
         }

      }
   }

   return ret;
}

void QListModeViewBase::dataChanged(const QModelIndex &, const QModelIndex &)
{
   dd->doDelayedItemsLayout();
}


QRect QListModeViewBase::mapToViewport(const QRect &rect) const
{
   if (isWrapping()) {
      return rect;
   }

   // If the listview is in "listbox-mode", the items are as wide as the view.
   // But we don't shrink the items.
   QRect result = rect;

   if (flow() == QListView::TopToBottom) {
      result.setLeft(spacing());
      result.setWidth(qMax(rect.width(), qMax(contentsSize.width(), viewport()->width()) - 2 * spacing()));
   } else { // LeftToRight
      result.setTop(spacing());
      result.setHeight(qMax(rect.height(), qMax(contentsSize.height(), viewport()->height()) - 2 * spacing()));
   }

   return result;
}

int QListModeViewBase::perItemScrollingPageSteps(int length, int bounds, bool wrap) const
{
   QVector<int> positions;

   if (wrap) {
      positions = segmentPositions;

   } else if (! flowPositions.isEmpty()) {
      positions.reserve(scrollValueMap.size());

      for (int itemShown : scrollValueMap) {
         positions.append(flowPositions.at(itemShown));
      }

   }

   if (positions.isEmpty() || bounds <= length) {
      return positions.count();
   }

   if (uniformItemSizes()) {
      for (int i = 1; i < positions.count(); ++i)  {
         if (positions.at(i) > 0) {
            return length / positions.at(i);
         }
      }

      return 0; // all items had height 0
   }

   int pageSteps = 0;
   int steps = positions.count() - 1;
   int max = qMax(length, bounds);
   int min = qMin(length, bounds);
   int pos = min - (max - positions.last());

   while (pos >= 0 && steps > 0) {
      pos -= (positions.at(steps) - positions.at(steps - 1));

      if (pos >= 0) {
         //this item should be visible
         ++pageSteps;
      }
      --steps;
   }

   // at this point we know that positions has at least one entry
   return qMax(pageSteps, 1);
}

int QListModeViewBase::perItemScrollToValue(int index, int scrollValue, int viewportSize,
   QAbstractItemView::ScrollHint hint,
   Qt::Orientation orientation, bool wrap, int itemExtent) const
{

   if (index < 0) {
      return scrollValue;
   }

   itemExtent += spacing();

   QVector<int> visibleFlowPositions;
   visibleFlowPositions.reserve(flowPositions.count() - 1);

   for (int i = 0; i < flowPositions.count() - 1; i++) { // flowPositions count is +1 larger than actual row count
      if (!isHidden(i)) {
         visibleFlowPositions.append(flowPositions.at(i));
      }
   }

   if (!wrap) {
      int topIndex = index;
      const int bottomIndex = topIndex;
      const int bottomCoordinate = visibleFlowPositions.at(index);

      while (topIndex > 0 &&
         (bottomCoordinate - visibleFlowPositions.at(topIndex - 1) + itemExtent) <= (viewportSize)) {
         topIndex--;
      }

      const int itemCount = bottomIndex - topIndex + 1;
      switch (hint) {
         case QAbstractItemView::PositionAtTop:
            return index;
         case QAbstractItemView::PositionAtBottom:
            return index - itemCount + 1;
         case QAbstractItemView::PositionAtCenter:
            return index - (itemCount / 2);
         default:
            break;
      }
   } else {
      // wrapping
      Qt::Orientation flowOrientation = (flow() == QListView::LeftToRight
            ? Qt::Horizontal : Qt::Vertical);
      if (flowOrientation == orientation) { // scrolling in the "flow" direction
         // ### wrapped scrolling in the flow direction
         return visibleFlowPositions.at(index); // ### always pixel based for now

      } else if (! segmentStartRows.isEmpty()) {
         // we are scrolling in the "segment" direction
         int segment = cs_vector_query(segmentStartRows, index, 0, segmentStartRows.count() - 1);

         int leftSegment            = segment;
         const int rightSegment     = leftSegment;
         const int bottomCoordinate = segmentPositions.at(segment);

         while (leftSegment > scrollValue && (bottomCoordinate - segmentPositions.at(leftSegment - 1) + itemExtent) <= (viewportSize)) {
            leftSegment--;
         }

         const int segmentCount = rightSegment - leftSegment + 1;
         switch (hint) {
            case QAbstractItemView::PositionAtTop:
               return segment;
            case QAbstractItemView::PositionAtBottom:
               return segment - segmentCount + 1;
            case QAbstractItemView::PositionAtCenter:
               return segment - (segmentCount / 2);
            default:
               break;
         }
      }
   }
   return scrollValue;
}

void QListModeViewBase::clear()
{
   flowPositions.clear();
   segmentPositions.clear();
   segmentStartRows.clear();
   segmentExtents.clear();
   batchSavedPosition = 0;
   batchStartRow = 0;
   batchSavedDeltaSeg = 0;
}

/*
 * IconMode ListView Implementation
*/

void QIconModeViewBase::setPositionForIndex(const QPoint &position, const QModelIndex &index)
{
   if (index.row() >= items.count()) {
      return;
   }

   const QSize oldContents = contentsSize;
   qq->update(index); // update old position
   moveItem(index.row(), position);
   qq->update(index); // update new position

   if (contentsSize != oldContents) {
      dd->viewUpdateGeometries();   // update the scroll bars
   }
}

void QIconModeViewBase::appendHiddenRow(int row)
{
   if (row >= 0 && row < items.count()) {
      //remove item
      tree.removeLeaf(items.at(row).rect(), row);
   }
   QCommonListViewBase::appendHiddenRow(row);
}

void QIconModeViewBase::removeHiddenRow(int row)
{
   QCommonListViewBase::removeHiddenRow(row);
   if (row >= 0 && row < items.count()) {
      //insert item
      tree.insertLeaf(items.at(row).rect(), row);
   }
}

#ifndef QT_NO_DRAGANDDROP
bool QIconModeViewBase::filterStartDrag(Qt::DropActions supportedActions)
{
   // This function does the same thing as in QAbstractItemView::startDrag(),
   // plus adding viewitems to the draggedItems list.
   // We need these items to draw the drag items
   QModelIndexList indexes = dd->selectionModel->selectedIndexes();
   if (indexes.count() > 0 ) {
      if (viewport()->acceptDrops()) {
         QModelIndexList::const_iterator it = indexes.constBegin();

         for (; it != indexes.constEnd(); ++it)
            if (dd->model->flags(*it) & Qt::ItemIsDragEnabled
               && (*it).column() == dd->column) {
               draggedItems.push_back(*it);
            }
      }

      QRect rect;
      QPixmap pixmap = dd->renderToPixmap(indexes, &rect);
      rect.adjust(horizontalOffset(), verticalOffset(), 0, 0);

      QDrag *drag = new QDrag(qq);
      drag->setMimeData(dd->model->mimeData(indexes));
      drag->setPixmap(pixmap);
      drag->setHotSpot(dd->pressedPosition - rect.topLeft());

      Qt::DropAction action = drag->exec(supportedActions, dd->defaultDropAction);
      draggedItems.clear();

      if (action == Qt::MoveAction) {
         dd->clearOrRemove();
      }
   }
   return true;
}

bool QIconModeViewBase::filterDropEvent(QDropEvent *e)
{
   if (e->source() != qq) {
      return false;
   }

   const QSize contents = contentsSize;
   QPoint offset(horizontalOffset(), verticalOffset());
   QPoint end = e->pos() + offset;
   if (qq->acceptDrops()) {
      const Qt::ItemFlags dropableFlags = Qt::ItemIsDropEnabled | Qt::ItemIsEnabled;
      const QVector<QModelIndex> &dropIndices = intersectingSet(QRect(end, QSize(1, 1)));

      for (const QModelIndex &index : dropIndices) {
         if ((index.flags() & dropableFlags) == dropableFlags) {
            return false;
         }
      }
   }
   QPoint start = dd->pressedPosition;
   QPoint delta = (dd->movement == QListView::Snap ? snapToGrid(end) - snapToGrid(start) : end - start);
   QList<QModelIndex> indexes = dd->selectionModel->selectedIndexes();
   for (int i = 0; i < indexes.count(); ++i) {
      QModelIndex index = indexes.at(i);
      QRect rect = dd->rectForIndex(index);
      viewport()->update(dd->mapToViewport(rect, false));
      QPoint dest = rect.topLeft() + delta;
      if (qq->isRightToLeft()) {
         dest.setX(dd->flipX(dest.x()) - rect.width());
      }
      moveItem(index.row(), dest);
      qq->update(index);
   }
   dd->stopAutoScroll();
   draggedItems.clear();
   dd->emitIndexesMoved(indexes);
   e->accept(); // we have handled the event
   // if the size has not grown, we need to check if it has shrinked
   if (contentsSize != contents) {
      if ((contentsSize.width() <= contents.width()
            || contentsSize.height() <= contents.height())) {
         updateContentsSize();
      }
      dd->viewUpdateGeometries();
   }
   return true;
}

bool QIconModeViewBase::filterDragLeaveEvent(QDragLeaveEvent *e)
{
   viewport()->update(draggedItemsRect()); // erase the area
   draggedItemsPos = QPoint(-1, -1); // don't draw the dragged items
   return QCommonListViewBase::filterDragLeaveEvent(e);
}

bool QIconModeViewBase::filterDragMoveEvent(QDragMoveEvent *e)
{
   if (e->source() != qq || !dd->canDrop(e)) {
      return false;
   }


   // ignore by default
   e->ignore();

   // get old dragged items rect
   QRect itemsRect = this->itemsRect(draggedItems);
   viewport()->update(itemsRect.translated(draggedItemsDelta()));

   // update position
   draggedItemsPos = e->pos();
   // get new items rect

   viewport()->update(itemsRect.translated(draggedItemsDelta()));

   // set the item under the cursor to current
   QModelIndex index;
   if (movement() == QListView::Snap) {
      QRect rect(snapToGrid(e->pos() + offset()), gridSize());
      const QVector<QModelIndex> intersectVector = intersectingSet(rect);
      index = intersectVector.count() > 0 ? intersectVector.last() : QModelIndex();
   } else {
      index = qq->indexAt(e->pos());
   }

   // check if we allow drops here
   if (draggedItems.contains(index)) {
      e->accept();   // allow changing item position
   } else if (dd->model->flags(index) & Qt::ItemIsDropEnabled) {
      e->accept();   // allow dropping on dropenabled items
   } else if (!index.isValid()) {
      e->accept();   // allow dropping in empty areas
   }

   // the event was treated. do autoscrolling
   if (dd->shouldAutoScroll(e->pos())) {
      dd->startAutoScroll();
   }
   return true;
}
#endif // QT_NO_DRAGANDDROP

void QIconModeViewBase::setRowCount(int rowCount)
{
   tree.create(qMax(rowCount - hiddenCount(), 0));
}

void QIconModeViewBase::scrollContentsBy(int dx, int dy, bool scrollElasticBand)
{
   if (scrollElasticBand) {
      dd->scrollElasticBandBy(isRightToLeft() ? -dx : dx, dy);
   }

   QCommonListViewBase::scrollContentsBy(dx, dy, scrollElasticBand);
   if (!draggedItems.isEmpty()) {
      viewport()->update(draggedItemsRect().translated(dx, dy));
   }
}

void QIconModeViewBase::dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
   if (column() >= topLeft.column() && column() <= bottomRight.column())  {
      QStyleOptionViewItem option = viewOptions();
      int bottom = qMin(items.count(), bottomRight.row() + 1);
      for (int row = topLeft.row(); row < bottom; ++row) {
         items[row].resize(itemSize(option, modelIndex(row)));
      }
   }
}

bool QIconModeViewBase::doBatchedItemLayout(const QListViewLayoutInfo &info, int max)
{
   if (info.last >= items.count()) {
      //first we create the items
      QStyleOptionViewItem option = viewOptions();
      for (int row = items.count(); row <= info.last; ++row) {
         QSize size = itemSize(option, modelIndex(row));
         QListViewItem item(QRect(0, 0, size.width(), size.height()), row); // default pos
         items.append(item);
      }
      doDynamicLayout(info);
   }
   return (batchStartRow > max); // done
}

QListViewItem QIconModeViewBase::indexToListViewItem(const QModelIndex &index) const
{
   if (index.isValid() && index.row() < items.count()) {
      return items.at(index.row());
   }
   return QListViewItem();
}

void QIconModeViewBase::initBspTree(const QSize &contents)
{
   // remove all items from the tree
   int leafCount = tree.leafCount();
   for (int l = 0; l < leafCount; ++l) {
      tree.leaf(l).clear();
   }
   // we have to get the bounding rect of the items before we can initialize the tree
   QBspTree::Node::Type type = QBspTree::Node::Both; // 2D
   // simple heuristics to get better bsp
   if (contents.height() / contents.width() >= 3) {
      type = QBspTree::Node::HorizontalPlane;
   } else if (contents.width() / contents.height() >= 3) {
      type = QBspTree::Node::VerticalPlane;
   }
   // build tree for the bounding rect (not just the contents rect)
   tree.init(QRect(0, 0, contents.width(), contents.height()), type);
}

QPoint QIconModeViewBase::initDynamicLayout(const QListViewLayoutInfo &info)
{
   int x, y;
   if (info.first == 0) {
      x = info.bounds.x() + info.spacing;
      y = info.bounds.y() + info.spacing;
      items.reserve(rowCount() - hiddenCount());
   } else {
      int idx = info.first - 1;
      while (idx > 0 && !items.at(idx).isValid()) {
         --idx;
      }
      const QListViewItem &item = items.at(idx);
      x = item.x;
      y = item.y;
      if (info.flow == QListView::LeftToRight) {
         x += (info.grid.isValid() ? info.grid.width() : item.w) + info.spacing;
      } else {
         y += (info.grid.isValid() ? info.grid.height() : item.h) + info.spacing;
      }
   }
   return QPoint(x, y);
}

/*!
  \internal
*/
void QIconModeViewBase::doDynamicLayout(const QListViewLayoutInfo &info)
{
   const bool useItemSize = !info.grid.isValid();
   const QPoint topLeft = initDynamicLayout(info);

   int segStartPosition;
   int segEndPosition;
   int deltaFlowPosition;
   int deltaSegPosition;
   int deltaSegHint;
   int flowPosition;
   int segPosition;

   if (info.flow == QListView::LeftToRight) {
      segStartPosition = info.bounds.left() + info.spacing;
      segEndPosition = info.bounds.right();
      deltaFlowPosition = info.grid.width(); // dx
      deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.height()); // dy
      deltaSegHint = info.grid.height();
      flowPosition = topLeft.x();
      segPosition = topLeft.y();
   } else { // flow == QListView::TopToBottom
      segStartPosition = info.bounds.top() + info.spacing;
      segEndPosition = info.bounds.bottom();
      deltaFlowPosition = info.grid.height(); // dy
      deltaSegPosition = (useItemSize ? batchSavedDeltaSeg : info.grid.width()); // dx
      deltaSegHint = info.grid.width();
      flowPosition = topLeft.y();
      segPosition = topLeft.x();
   }

   if (moved.count() != items.count()) {
      moved.resize(items.count());
   }

   QRect rect(QPoint(), topLeft);
   QListViewItem *item = nullptr;
   for (int row = info.first; row <= info.last; ++row) {
      item = &items[row];
      if (isHidden(row)) {
         item->invalidate();
      } else {
         // if we are not using a grid, we need to find the deltas
         if (useItemSize) {
            if (info.flow == QListView::LeftToRight) {
               deltaFlowPosition = item->w + info.spacing;
            } else {
               deltaFlowPosition = item->h + info.spacing;
            }
         } else {
            item->w = qMin(info.grid.width(), item->w);
            item->h = qMin(info.grid.height(), item->h);
         }

         // create new segment
         if (info.wrap
            && flowPosition + deltaFlowPosition > segEndPosition
            && flowPosition > segStartPosition) {
            flowPosition = segStartPosition;
            segPosition += deltaSegPosition;
            if (useItemSize) {
               deltaSegPosition = 0;
            }
         }
         // We must delay calculation of the seg adjustment, as this item
         // may have caused a wrap to occur
         if (useItemSize) {
            if (info.flow == QListView::LeftToRight) {
               deltaSegHint = item->h + info.spacing;
            } else {
               deltaSegHint = item->w + info.spacing;
            }
            deltaSegPosition = qMax(deltaSegPosition, deltaSegHint);
         }

         // set the position of the item
         // ### idealy we should have some sort of alignment hint for the item
         // ### (normally that would be a point between the icon and the text)
         if (!moved.testBit(row)) {
            if (info.flow == QListView::LeftToRight) {
               if (useItemSize) {
                  item->x = flowPosition;
                  item->y = segPosition;
               } else { // use grid
                  item->x = flowPosition + ((deltaFlowPosition - item->w) / 2);
                  item->y = segPosition;
               }
            } else { // TopToBottom
               if (useItemSize) {
                  item->y = flowPosition;
                  item->x = segPosition;
               } else { // use grid
                  item->y = flowPosition + ((deltaFlowPosition - item->h) / 2);
                  item->x = segPosition;
               }
            }
         }

         // let the contents contain the new item
         if (useItemSize) {
            rect |= item->rect();
         } else if (info.flow == QListView::LeftToRight) {
            rect |= QRect(flowPosition, segPosition, deltaFlowPosition, deltaSegPosition);
         } else { // flow == TopToBottom
            rect |= QRect(segPosition, flowPosition, deltaSegPosition, deltaFlowPosition);
         }

         // prepare for next item
         flowPosition += deltaFlowPosition; // current position + item width + gap
      }
   }
   batchSavedDeltaSeg = deltaSegPosition;
   batchStartRow = info.last + 1;
   bool done = (info.last >= rowCount() - 1);
   // resize the content area
   if (done || !info.bounds.contains(item->rect())) {
      contentsSize = rect.size();
      if (info.flow == QListView::LeftToRight) {
         contentsSize.rheight() += info.spacing;
      } else {
         contentsSize.rwidth() += info.spacing;
      }
   }
   if (rect.size().isEmpty()) {
      return;
   }
   // resize tree
   int insertFrom = info.first;
   if (done || info.first == 0) {
      initBspTree(rect.size());
      insertFrom = 0;
   }
   // insert items in tree
   for (int row = insertFrom; row <= info.last; ++row) {
      tree.insertLeaf(items.at(row).rect(), row);
   }
   // if the new items are visble, update the viewport
   QRect changedRect(topLeft, rect.bottomRight());
   if (clipRect().intersects(changedRect)) {
      viewport()->update();
   }
}

QVector<QModelIndex> QIconModeViewBase::intersectingSet(const QRect &area) const
{
   QIconModeViewBase *that = const_cast<QIconModeViewBase *>(this);
   QBspTree::Data data(static_cast<void *>(that));
   QVector<QModelIndex> res;
   that->interSectingVector = &res;
   that->tree.climbTree(area, &QIconModeViewBase::addLeaf, data);
   that->interSectingVector = nullptr;
   return res;
}

QRect QIconModeViewBase::itemsRect(const QVector<QModelIndex> &indexes) const
{
   QVector<QModelIndex>::const_iterator it = indexes.begin();
   QListViewItem item = indexToListViewItem(*it);
   QRect rect(item.x, item.y, item.w, item.h);
   for (; it != indexes.end(); ++it) {
      item = indexToListViewItem(*it);
      rect |= viewItemRect(item);
   }
   return rect;
}

int QIconModeViewBase::itemIndex(const QListViewItem &item) const
{
   if (!item.isValid()) {
      return -1;
   }

   int i = item.indexHint;
   if (i < items.count()) {
      if (items.at(i) == item) {
         return i;
      }
   } else {
      i = items.count() - 1;
   }

   int j = i;
   int c = items.count();
   bool a = true;
   bool b = true;

   while (a || b) {
      if (a) {
         if (items.at(i) == item) {
            items.at(i).indexHint = i;
            return i;
         }
         a = ++i < c;
      }
      if (b) {
         if (items.at(j) == item) {
            items.at(j).indexHint = j;
            return j;
         }
         b = --j > -1;
      }
   }
   return -1;
}

void QIconModeViewBase::addLeaf(QVector<int> &leaf, const QRect &area,
   uint visited, QBspTree::Data data)
{
   QListViewItem *vi;
   QIconModeViewBase *_this = static_cast<QIconModeViewBase *>(data.ptr);
   for (int i = 0; i < leaf.count(); ++i) {
      int idx = leaf.at(i);
      if (idx < 0 || idx >= _this->items.count()) {
         continue;
      }
      vi = &_this->items[idx];
      Q_ASSERT(vi);
      if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) {
         QModelIndex index  = _this->dd->listViewItemToIndex(*vi);
         Q_ASSERT(index.isValid());
         _this->interSectingVector->append(index);
         vi->visited = visited;
      }
   }
}

void QIconModeViewBase::moveItem(int index, const QPoint &dest)
{
   // does not impact on the bintree itself or the contents rect
   QListViewItem *item = &items[index];
   QRect rect = item->rect();

   // move the item without removing it from the tree
   tree.removeLeaf(rect, index);
   item->move(dest);
   tree.insertLeaf(QRect(dest, rect.size()), index);

   // resize the contents area
   contentsSize = (QRect(QPoint(0, 0), contentsSize) | QRect(dest, rect.size())).size();

   // mark the item as moved
   if (moved.count() != items.count()) {
      moved.resize(items.count());
   }
   moved.setBit(index, true);
}

QPoint QIconModeViewBase::snapToGrid(const QPoint &pos) const
{
   int x = pos.x() - (pos.x() % gridSize().width());
   int y = pos.y() - (pos.y() % gridSize().height());
   return QPoint(x, y);
}

QPoint QIconModeViewBase::draggedItemsDelta() const
{
   if (movement() == QListView::Snap) {
      QPoint snapdelta = QPoint((offset().x() % gridSize().width()),
            (offset().y() % gridSize().height()));
      return snapToGrid(draggedItemsPos + snapdelta) - snapToGrid(pressedPosition()) - snapdelta;
   }
   return draggedItemsPos - pressedPosition();
}

QRect QIconModeViewBase::draggedItemsRect() const
{
   QRect rect = itemsRect(draggedItems);
   rect.translate(draggedItemsDelta());
   return rect;
}

void QListViewPrivate::scrollElasticBandBy(int dx, int dy)
{
   if (dx > 0) { // right
      elasticBand.moveRight(elasticBand.right() + dx);
   } else if (dx < 0) { // left
      elasticBand.moveLeft(elasticBand.left() - dx);
   }
   if (dy > 0) { // down
      elasticBand.moveBottom(elasticBand.bottom() + dy);
   } else if (dy < 0) { // up
      elasticBand.moveTop(elasticBand.top() - dy);
   }
}

void QIconModeViewBase::clear()
{
   tree.destroy();
   items.clear();
   moved.clear();
   batchStartRow = 0;
   batchSavedDeltaSeg = 0;
}

void QIconModeViewBase::updateContentsSize()
{
   QRect bounding;
   for (int i = 0; i < items.count(); ++i) {
      bounding |= items.at(i).rect();
   }
   contentsSize = bounding.size();
}

/*!
  \reimp
*/
void QListView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
#ifndef QT_NO_ACCESSIBILITY
   if (QAccessible::isActive()) {

      if (current.isValid()) {
         int entry = visualIndex(current);

         QAccessibleEvent event(this, QAccessible::Focus);
         event.setChild(entry);
         QAccessible::updateAccessibility(&event);
      }
   }
#endif
   QAbstractItemView::currentChanged(current, previous);
}

/*!
  \reimp
*/
void QListView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
#ifndef QT_NO_ACCESSIBILITY
   if (QAccessible::isActive()) {
      // ### does not work properly for selection ranges.
      QModelIndex sel = selected.indexes().value(0);
      if (sel.isValid()) {
         int entry = visualIndex(sel);
         QAccessibleEvent event(this, QAccessible::SelectionAdd);
         event.setChild(entry);
         QAccessible::updateAccessibility(&event);
      }

      QModelIndex desel = deselected.indexes().value(0);
      if (desel.isValid()) {
         int entry = visualIndex(desel);
         QAccessibleEvent event(this, QAccessible::SelectionRemove);
         event.setChild(entry);
         QAccessible::updateAccessibility(&event);
      }
   }
#endif

   QAbstractItemView::selectionChanged(selected, deselected);
}

int QListView::visualIndex(const QModelIndex &index) const
{
   Q_D(const QListView);

   d->executePostedLayout();

   QListViewItem itm = d->indexToListViewItem(index);
   int visualIndex   = d->commonListView->itemIndex(itm);

   for (int row = 0; row <= index.row() && visualIndex >= 0; row++) {
      if (d->isHidden(row)) {
         visualIndex--;
      }
   }

   return visualIndex;
}

QSize QListView::viewportSizeHint() const
{
   return QAbstractItemView::viewportSizeHint();
}

#endif // QT_NO_LISTVIEW
